
#define NOMINMAX
#include "boost_asio_network.h"

#include <boost/bind.hpp>

socketx::socketx(boost::asio::io_service& inio,
			   boost::shared_ptr<boost::asio::ip::tcp::socket> ins)
			   :io(inio),s(ins),open(true),async_ready(false),close_handle_(NULL)
{
	assert(s);
}

socketx::~socketx()
{
   HandleClose();
}

bool socketx::is_open()
{
	return open;
}

void socketx::start_async()
{
	assert(open);
	if(async_ready) 
		return;
	async_ready = true;

	boost::asio::async_read(
		*s,
		boost::asio::buffer(&recvSize,sizeof(recvSize)),
		boost::bind(
		   &socketx::HandleReadHeader,
		   this,
		   boost::asio::placeholders::error
		   )
	 );
}

bool socketx::recv(packet& in)
{
	if(!open) return false;
	if(async_ready) return false;
	
	in.Clear();

	try
	{
		boost::asio::read(*s,
			boost::asio::buffer(&recvSize,sizeof(recvSize)));
		if(recvSize > packet::MAX_SIZE)
		{
			HandleClose();
			return false;
		}
	}
	catch (boost::system::system_error& e)
	{
		HandleClose();
        return false;
	}

	return true;
}

bool socketx::recv_async(packet& in)
{
	if(!async_ready) return false;
	if(!open) return false;

	in.Clear();
	return recv_datas.Pop(in);
}

bool socketx::send(const packet& out)
{
  if(!open) return false;
  if(async_ready) return false;

  packet::size_type n = out.Size();
  std::memcpy(sendBuffer,&n,sizeof(packet::size_type));
  std::memcpy(sendBuffer+sizeof(packet::size_type),out.Data(),out.Size());

  try
  {
	  boost::asio::write(*s,
		  boost::asio::buffer(
		  sendBuffer,out.Size()+sizeof(packet::size_type)));
  }
  catch(boost::system::system_error& e)
  {
	  HandleClose();
	  return false;
  }

  return false;

}
	
bool socketx::send_async(const packet& out)
{
	if(!async_ready) return false;
	if(!open) return false;

	packet::size_type n = out.Size();
	std::memcpy(sendBuffer,&n,sizeof(packet::size_type));
	std::memcpy(sendBuffer+sizeof(packet::size_type),out.Data(),out.Size());

	boost::asio::async_write(
		*s,
		boost::asio::buffer(sendBuffer,out.Size()+sizeof(packet::size_type)),
		boost::bind(
		  &socketx::HandleWrite,
		  this,
		  boost::asio::placeholders::error)
		);
	
	return true;
}
void socketx::set_close_handle(sokcet_close_handle close_handle)
{
  close_handle_ = close_handle;
}

void socketx::close()
{
	io.post(boost::bind(&socketx::HandleClose,this));
}

void socketx::HandleWrite(const boost::system::error_code& e)
{
  if(e)
  {
	  io.post(boost::bind(&socketx::HandleClose,this));
  }
}

void socketx::HandleReadHeader(const boost::system::error_code &e)
{
	if(!e)
	{
		boost::asio::async_read(
			*s,
			boost::asio::buffer(recvBuffer,recvSize),
			boost::bind(
			  &socketx::HandleReadBody,
			  this,
			  boost::asio::placeholders::error)
			);
	}
	else
	{
		HandleClose();
	}
}

void socketx::HandleReadBody(const boost::system::error_code& e)
{
	if(!e)
	{
		packet p(recvBuffer,recvSize);
		recv_datas.Push(p);

		boost::asio::async_read(
			*s,
			boost::asio::buffer(&recvSize,sizeof(recvSize)),
			boost::bind(
			  &socketx::HandleReadHeader,
			  this,
			  boost::asio::placeholders::error
			)
		 );
	}
	else
	{
		HandleClose();
	}
}

void socketx::HandleClose()
{
	open = false;
	if(close_handle_)
      close_handle_();
	s->close();
}



network::network()
{

}

network::~network()
{
	stop();
}

bool network::connect(const char* addr, const char* port,connect_callback onconnect)
{
	assert(addr);
	assert(port);
	if(!addr || !port) return false;

	boost::asio::ip::tcp::resolver resolver(io);
	boost::asio::ip::tcp::resolver::query query(addr,port);
	boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query);

	boost::shared_ptr<boost::asio::ip::tcp::socket> s(new boost::asio::ip::tcp::socket(io));
	boost::asio::ip::tcp::resolver::iterator end;
	boost::system::error_code e = boost::asio::error::host_not_found;
	boost::shared_ptr<socketx> q;

	bool found = false;
	while(!found && i != end)
	{
		s->close();
		s->connect(*i++,e);

		if(!e)
		{
			found = true;
			if(onconnect)
				onconnect(new socketx(io,s),found,found);
		}
	}

	if(!found) return 0;

 	t = boost::shared_ptr<boost::thread>(new boost::thread(
 		boost::bind(&boost::asio::io_service::run,&io))
 		);

	return true;

}



bool network::accept(unsigned short port,accept_callback newclient_call,unsigned max)
{
	assert(port > 1024);
	assert(newclient_call || max > 0);
	if(port <= 1024) return false;
	if(!newclient_call && !max) return false;

	acceptor_ = boost::shared_ptr< boost::asio::ip::tcp::acceptor>( 
		new boost::asio::ip::tcp::acceptor(io,
		boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),port)));

	bool done = false;
	bool accept = true;
	unsigned count = 0;

	boost::shared_ptr<boost::asio::ip::tcp::socket> s(new boost::asio::ip::tcp::socket(io));
	newclient_call_ = newclient_call;
	max_ = max;
	acceptor_->async_accept(*s,
		boost::bind(&network::handle_accept, this,s,
		boost::asio::placeholders::error));

	t = boost::shared_ptr< boost::thread> (new boost::thread(
		boost::bind(&boost::asio::io_service::run,&io))
		);
     return true;
}
	


bool network::session_start(accept_callback newclient_call,unsigned max,
                              bool baccept,bool done,boost::shared_ptr<boost::asio::ip::tcp::socket> s)
{
	if(s.get())
	{
	    baccept = true; done = true;
	    if(newclient_call) newclient_call(new socketx(io, s),baccept,done);
	}
    return true;
}

bool network::handle_accept(boost::shared_ptr<boost::asio::ip::tcp::socket> socket_ptr,const boost::system::error_code& error)
{
	if (!error)
	{
		session_start(newclient_call_,max_,true,true,socket_ptr);
 		boost::shared_ptr<boost::asio::ip::tcp::socket> s2(new boost::asio::ip::tcp::socket(io));			
  		acceptor_->async_accept(*s2,
  			boost::bind(&network::handle_accept, this, s2,
  			boost::asio::placeholders::error));
	}
	return true;
}


void network::stop() 
{
	if(t.get())
		t.get()->join();
}

extern "C"
{
DQ_API Inetwork * CreateNetwork()
{
	static network s;
	return &s;
}
}